<?php

/**
 * @author Martin Pernica
 * @copyright 2009
 * @license Do not use without VisionCMS or VFramework !
 */

class Form
{

    public $output = '';
    public $label_separator = ':';
    public $useJs = true;
    public $useAjax = false; //TODO:Create AJAX support
    public $ajaxTargetDiv = '';
    private $raw = true;
    private $message_format = '<p class="form_message">%e</p>';
    private $view_state = '';

    public $valide_errors = array();

    #Captcha

    private $captcha_q = array();
    private $captcha_a = array();

    #Rules

    public $regex = array();
    public $regex_message = array();

    public $numeric = array();
    public $numeric_message = array();

    public $notempty = array();
    public $notempty_message = array();

    public $equal = array();
    public $equal_message = array();

    public $min = array();
    public $min_message = array();

    #Styles

    var $style1;
    var $style2;
    var $style3;
    var $style4;
    var $top;
    var $bottom;

    protected $submit_name;
    public $js_onsubmit_name = '__FormOnSubmit';
    static public $instance = 0;
    public $js_helper;

    public function __construct($action = '', $method = 'post', $useJs = true, $style =
        'table', $id = '', $class = '', $enctype = '')
    {
        self::$instance++;
        $this->js_helper = new Form_javascript_helper(self::$instance, $this->
            js_onsubmit_name, true);
        $this->js_onsubmit_name .= self::$instance;
        $this->useJs = $useJs;

        $this->view_state = @$_POST['__form_view_state'];

        $action = (empty($action)) ? '' : ' action="' . $action . '"';
        $method = (empty($method)) ? '' : ' method="' . $method . '"';
        $enctype = (empty($enctype)) ? '' : ' enctype="' . $enctype . '" ';
        $id = (empty($id)) ? '' : ' id="' . $id . '" ';
        $class = (empty($class)) ? '' : ' class="' . $class . '" ';
        $onSubmit = ($this->useJs) ? ' onSubmit="return ' . $this->js_onsubmit_name .
            '();" ' : '';

        $this->setStyle($style);

        $this->output .= "\n<form " . $action . $method . $enctype . $id . $class . $onSubmit .
            " >\n" . $this->top . "\n";

    }

    public function setStyle($style)
    {
        switch ($style) {
            case 'table':
                $this->top = '<table>';
                $this->bottom = '</table>';
                $this->style1 = '<tr><td>';
                $this->style2 = '</td></tr>';
                $this->style3 = '</td><td>';
                $this->style4 = '<tr><td><h3>%group</h3></td></tr>';
                break;

            case 'paragraph':
                $this->style1 = '<p>';
                $this->style2 = '</p>';
                $this->style3 = '';
                $this->style4 = '<h3>%group</h3>';
                break;

            case 'ulist':
                $this->top = '<ul>';
                $this->bottom = '</ul>';
                $this->style1 = '<li>';
                $this->style2 = '</li>';
                $this->style3 = '';
                $this->style4 = '<li><h3>%group</h3></li>';
                break;

            case 'fieldset':
                $this->top = '<fieldset>';
                $this->bottom = '</fieldset>';
                $this->style1 = '<p>';
                $this->style2 = '</p>';
                $this->style3 = '';
                $this->style4 = '<legend>%group</legend>';
                break;

            default:
                $this->top = '<table>';
                $this->bottom = '</table>';
                $this->style1 = '<tr><td>';
                $this->style2 = '</td></tr>';
                $this->style3 = '</td><td>';
                $this->style4 = '<tr><td><h3>%group</h3></td></tr>';
                break;

        }
    }

    public function addElement($element, $attributes = array(), $values = array())
    {
        if (is_array($attributes)) {
            switch ($element) {

                case 'input':
                    $this->_input($attributes);
                    break;

                case 'select':
                    $this->_select($attributes, $values);
                    break;

                case 'checkbox':
                    $this->_checkbox($attributes);
                    break;

                case 'textarea':
                    $this->_textarea($attributes);
                    break;

                case 'radio':
                    $this->_radio($attributes);
                    break;
            }
        } else {
            $this->output .= '<p>Error <strong>$attributes</strong> must be <strong>array</strong> !</p>';
        }
    }

    public function addGroup($title)
    {
        $this->output .= str_replace('%group', $title, $this->style4) . "\n";
    }

    public function addTextbox($name = '', $label = '', $value = '', $readonly = '',
        $class = '', $id = '')
    {
        $this->_input(array('type' => 'text', 'name' => $name, 'label' => $label,
            'value' => $value, 'readonly' => $readonly, 'class' => $class, 'id' => $id));

        return $this->getHelper($name);
    }

    public function addHidden($name = '', $value = '', $class = '', $id = '')
    {
        $this->_input(array('type' => 'hidden', 'name' => $name, 'value' => $value,
            'class' => $class, 'id' => $id));

        return $this->getHelper($name);
    }

    public function addPassword($name = '', $label = '', $value = '', $readonly = '',
        $class = '', $id = '')
    {
        $this->_input(array('type' => 'password', 'name' => $name, 'label' => $label,
            'value' => $value, 'readonly' => $readonly, 'class' => $class, 'id' => $id));

        return $this->getHelper($name);
    }

    public function addCheckbox($name = '', $label = '', $value = '', $checked = 0,
        $class = '', $id = '')
    {
        $this->_checkbox(array('name' => $name, 'label' => $label, 'value' => $value,
            'checked' => $checked, 'class' => $class, 'id' => $id));

        return $this->getHelper($name, "checkbox");
    }

    public function addTextarea($name = '', $label = '', $value = '', $rows = 7, $cols =
        40, $class = '', $id = '')
    {
        $this->_textarea(array('name' => $name, 'label' => $label, 'value' => $value,
            'rows' => $rows, 'cols' => $cols, 'class' => $class, 'id' => $id));

        return $this->getHelper($name, "textarea");
    }

    public function addRadio($name = '', $label = '', $value = '', $checked = 0, $class =
        '', $id = '')
    {
        $this->_radio(array('name' => $name, 'label' => $label, 'value' => $value,
            'checked' => $checked, 'class' => $class, 'id' => $id));

        return $this->getHelper($name, "checkbox");
    }

    public function addSelect($name = '', $label = '', $values = array(), $selected =
        0, $class = '', $id = '')
    {
        $this->_select(array('name' => $name, 'label' => $label, 'selected' => $selected,
            'class' => $class, 'id' => $id), $values);

        //return $this->getHelper($name, "checkbox");
    }

    public function addSubmit($name = '', $value = '')
    {
        $this->_input(array('type' => 'submit', 'name' => $name, 'value' => $value));
    }

    public function addReset($name = '', $value = '')
    {
        $this->_input(array('type' => 'reset', 'name' => $name, 'value' => $value));
    }

    public function _input($attributes)
    {
        $type = (empty($attributes['type'])) ? '' : ' type="' . $attributes['type'] .
            '" ';
        $name = (empty($attributes['name'])) ? '' : ' name="' . $attributes['name'] .
            '" ';
        $value = (empty($attributes['value'])) ? '' : ' value="' . $attributes['value'] .
            '"';
        $disabled = (empty($attributes['disabled'])) ? '' : ' disabled="' . $attributes['disabled'] .
            '" ';
        $readonly = (empty($attributes['readonly'])) ? '' : ' readonly="' . $attributes['readonly'] .
            '" ';
        $id = (empty($attributes['id'])) ? '' : 'id="' . $attributes['id'] . '" ';
        $class = (empty($attributes['class'])) ? '' : ' class="' . $attributes['class'] .
            '" ';

        $label = (empty($attributes['label'])) ? '' : $attributes['label'] . $this->
            label_separator . " ";

        if ($attributes['type'] == 'submit') {
            $this->submit_name = $attributes['name'];
        }

        $this->output .= $this->style1 . $label . $this->style3 . "<input" . $type . $name .
            $value . $disabled . $readonly . $id . "/>" . $this->style2 . "\n";
    }

    public function _select($attributes, $values)
    {
        $name = (empty($attributes['name'])) ? '' : ' name="' . $attributes['name'] .
            '" ';
        $multiple = (empty($attributes['multiple'])) ? '' : ' multiple="multiple"';
        $size = (empty($attributes['size'])) ? '' : ' size="' . $attributes['size'] .
            '" ';
        $disabled = (empty($attributes['disabled'])) ? '' : ' disabled="disabled" ';

        $label = (empty($attributes['label'])) ? '' : $attributes['label'] . $this->
            label_separator . " ";
        $options = "";

        foreach ($values as $key => $val) {

            if (is_array($val)) {
                $options .= "\n<optgroup label=\"" . $key . "\">\n";
                foreach ($val as $key2 => $val2) {

                    if ($attributes['selected'] == $val2) {
                        $selected2 = " selected=\"selected\" ";
                    } else {
                        $selected2 = null;
                    }

                    $options .= "<option value=\"" . $val2 . "\"" . $selected2 . ">" . $key2 .
                        "</option>\n";

                }
                $options .= "\n</optgroup>\n";
            } else {

                if ($attributes['selected'] == $val) {
                    $selected = " selected=\"selected\" ";
                } else {
                    $selected = null;
                }

                $options .= "<option value=\"" . $val . "\"" . $selected . ">" . $key .
                    "</option>\n";
            }
        }

        $select = $label . $this->style3 . "<select" . $name . $size . $multiple . $disabled .
            ">\n" . $options . "\n</select>";

        $this->output .= $this->style1 . $select . $this->style2 . "\n";

    }

    public function _checkbox($attributes)
    {
        $name = (empty($attributes['name'])) ? '' : ' name="' . $attributes['name'] .
            '" ';
        $value = (empty($attributes['value'])) ? '' : ' value="' . $attributes['value'] .
            '" ';
        $id = (empty($attributes['id'])) ? '' : ' id="' . $attributes['id'] . '" ';
        $class = (empty($attributes['class'])) ? '' : ' class="' . $attributes['class'] .
            '" ';
        $checked = ($attributes['checked'] == 0) ? '' : ' checked="checked" ';

        $label = (empty($attributes['label'])) ? '' : $attributes['label'] . $this->
            label_separator . " ";

        $checkbox = $label . $this->style3 . "<input type=\"checkbox\"" . $checked . $name .
            $value . $id . $class . "/>";

        $this->output .= $this->style1 . $checkbox . $this->style2 . "\n";

    }

    public function _textarea($attributes)
    {
        $name = (empty($attributes['name'])) ? '' : ' name="' . $attributes['name'] .
            '" ';
        $value = (empty($attributes['value'])) ? '' : $attributes['value'];
        $id = (empty($attributes['id'])) ? '' : ' id="' . $attributes['id'] . '" ';
        $class = (empty($attributes['class'])) ? '' : ' class="' . $attributes['class'] .
            '" ';
        $rows = (empty($attributes['rows'])) ? '' : ' rows="' . intval($attributes['rows']) .
            '" ';
        $cols = (empty($attributes['cols'])) ? '' : ' cols="' . intval($attributes['cols']) .
            '" ';

        $label = (empty($attributes['label'])) ? '' : $attributes['label'] . $this->
            label_separator . " ";

        $textarea = $label . $this->style3 . "<textarea" . $rows . $cols . $name . $id .
            $class . ">";
        $textarea .= $value;
        $textarea .= "</textarea>\n";

        $this->output .= $this->style1 . $textarea . $this->style2 . "\n";
    }

    public function _radio($attributes)
    {
        $name = (empty($attributes['name'])) ? '' : ' name="' . $attributes['name'] .
            '" ';
        $value = (empty($attributes['value'])) ? '' : ' value="' . $attributes['value'] .
            '" ';
        $id = (empty($attributes['id'])) ? '' : ' id="' . $attributes['id'] . '" ';
        $class = (empty($attributes['class'])) ? '' : ' class="' . $attributes['class'] .
            '" ';
        $checked = ($attributes['checked'] == 0) ? '' : ' checked="checked" ';


        $label = (empty($attributes['label'])) ? '' : $attributes['label'] . $this->
            label_separator . " ";


        $checkbox = $label . $this->style3 . "<input type=\"radio\"" . $checked . $name .
            $value . $id . $class . "/>";


        $this->output .= $this->style1 . $checkbox . $this->style2 . "\n";


    }

    public function addLine($html, $with_style = true)
    {
        $this->output .= ($with_style) ? $this->style1 . $html . $this->style2 : $html;
        $this->output .= "\n";
    }

    public function addCaptcha()
    {
        $size = sizeof($this->captcha_q) - 1;
        $random = rand(0, $size);

        $view_state = $this->viewState('get');

        $view_state['__captcha_random_last'] = $view_state['__captcha_random'];
        $view_state['__captcha_random'] = $random;

        $this->viewState('set', $view_state);

        $this->addTextbox('__captcha', $this->captcha_q[$random]);
    }

    public function render()
    {
        $this->addHidden('__form_view_state', $this->view_state);
        $this->output .= $this->bottom . "</form>\n";
        $this->output .= ($this->useJs) ? Form_javascript_helper::getJs() : "\n";
        if ($this->raw) {
            echo $this->output;
        } else {
            return $this->output;
        }
    }

    public function addQuestion($q, $a)
    {
        $this->captcha_q[] = $q;
        $this->captcha_a[] = $a;
    }

    public function setOutput($type)
    {
        switch ($type) {
            case 0:
                $this->raw = false;
                break;

            case 1:
                $this->raw = true;
                break;
        }
    }

    public function setMessageFormat($format)
    {
        $this->message_format = $format;
    }

    private function _messageOut($message)
    {
        if (!empty($message)) {
            $message = str_replace('%e', $message, $this->message_format);

            if ($this->raw == 0) {
                $this->valide_errors[] = $message;
            } else {
                echo $message;
            }
        }
    }

    public function getMessage()
    {
        return $this->valide_errors;
    }

    private function getHelper($name, $type = 'textbox')
    {
        return new Form_helper($this, $name, $this->js_helper, $type);
    }

    private function viewState($action, $view = '')
    {
        if ($action == 'get') {
            $view_state = base64_decode($this->view_state);
            $view_state = unserialize($view_state);

            return $view_state;
        } elseif ($action == 'set') {

            $view_state = serialize($view);
            $view_state = base64_encode($view_state);
            $this->view_state = $view_state;

            return true;
        }
    }

    public function isSubmitted()
    {
        if (isset($_POST[$this->submit_name])) {
            return true;
        } else {
            return false;
        }
    }

    public function isHuman()
    {
        $view_state = $this->viewState('get');
        if ($_POST['__captcha'] == $this->captcha_a[$view_state['__captcha_random_last']]) {
            return true;
        } else {
            return false;
        }
    }

    public function isValid()
    {
        $req = sizeof($this->regex);
        $cur = 0;

        $req2 = sizeof($this->numeric);
        $cur2 = 0;

        $req3 = sizeof($this->notempty);
        $cur3 = 0;

        $req4 = sizeof($this->equal);
        $cur4 = 0;

        $req5 = sizeof($this->min);
        $cur5 = 0;

        if ($req != 0) {

            foreach ($this->regex as $name => $regex) {
                if (preg_match($regex, $_POST[$name])) {
                    $cur++;
                } else {
                    $this->_messageOut($this->regex_message[$name]);
                }
            }
        }

        if ($req2 != 0) {

            foreach ($this->numeric as $name => $range) {
                if ($range == 0) {

                    if (is_numeric($_POST[$name])) {
                        $cur2++;
                    } else {
                        $this->_messageOut($this->numeric_message[$name]);
                    }

                } else {
                    $range = explode(',', $range);
                    $max = range($range[0], $range[1]);

                    if (is_numeric($_POST[$name])) {

                        if (array_search($_POST[$name], $max) !== false) {
                            $cur2++;
                        } else {
                            $this->_messageOut($this->numeric_message[$name]);
                        }

                    } else {
                        $this->_messageOut($this->numeric_message[$name]);
                    }

                }
            }
        }

        if ($req3 != 0) {

            foreach ($this->notempty as $name => $val) {
                if (!empty($_POST[$name])) {
                    $cur3++;
                } else {
                    $this->_messageOut($this->notempty_message[$name]);
                }
            }
        }

        if ($req4 != 0) {

            foreach ($this->equal as $name => $to) {
                if ($_POST[$name] == $to) {
                    $cur4++;
                } else {
                    $this->_messageOut($this->equal_message[$name]);
                }
            }
        }

        if ($req5 != 0) {
            foreach ($this->min as $name => $min) {
                if (strlen($_POST[$name]) >= $min) {
                    $cur4++;
                } else {
                    $this->_messageOut($this->min_message[$name]);
                }
            }
        }

        if ($req + $req2 + $req3 + $req4 + $req5 == $cur + $cur2 + $cur3 + $cur4 + $cur5) {
            return true;
        } else {
            return false;
        }

    }

    public function __toString()
    {
        $out = $this->output;
        $this->setOutput(0);
        $return = $this->render();
        $this->setOutput($out);

        return $return;
    }
}

class Form_helper
{

    private $name;
    protected $form;
    protected $form_js;
    private $type;

    public function __construct($form_instance, $name, $form_js, $type = "textbox")
    {
        $this->name = $name;
        $this->form = $form_instance;
        $this->type = $type;
        $this->form_js = $form_js;
    }

    public function addRegex($regex, $message = '', $useJs = true)
    {
        $this->form->regex[$this->name] = $regex;
        $this->form->regex_message[$this->name] = $message;

        if ($useJs && $this->form->useJs)
            $this->form_js->createIfForRegex($this->name, $regex, $message);

        return $this;
    }

    public function setMinLength($min = 0, $message = '', $useJs = true)
    {
        $message = str_replace('%i', $min, $message);
        $this->form->min[$this->name] = $min;
        $this->form->min_message[$this->name] = $message;

        if ($useJs && $this->form->useJs)
            $this->form_js->createMinLength($this->name, $min, $message);

        return $this;
    }

    public function onlyNumeric($range = 0, $message = '', $useJs = true)
    {
        if ($range == 0) {
            $this->form->numeric[$this->name] = 0;
            $this->form->numeric_message[$this->name] = $message;
        } else {
            $this->form->numeric[$this->name] = $range;
            $this->form->numeric_message[$this->name] = $message;
        }

        if ($useJs && $this->form->useJs)
            $this->form_js->createIfForIsNumeric($this->name, $message);

        return $this;
    }

    public function notEmpty($message = '', $useJs = true)
    {
        $this->form->notempty[$this->name] = '';
        $this->form->notempty_message[$this->name] = $message;

        if ($useJs && $this->form->useJs) {
            if ($this->type == "textarea") {
                $this->form_js->createIfForTextarea($this->name, $message, "==", "", false);
            } else {
                $this->form_js->createIf($this->name, "==", '', $message);
            }
        }

        return $this;
    }

    public function equal($to, $message = '', $useJs = true)
    {
        $this->form->equal[$this->name] = $to;
        $this->form->equal_message[$this->name] = $message;

        if ($useJs && $this->form->useJs) {
            if ($this->type == "checkbox") {
                $this->form_js->createIfForCheckbox($this->name, $message);
            } elseif ($this->type == "textarea") {
                $this->form_js->createIfForTextarea($this->name, $message, "!=", $to, false);
            } else {
                $this->form_js->createIf($this->name, "!=", $to, $message);
            }
        }

        return $this;
    }

}

class Form_javascript_helper
{

    static public $js_top = "";
    static public $js_content = "";
    static public $js_bottom;
    static public $js_vars = "\nvar __checksum = 0;\n";

    static private $vars = array();
    static private $_count = 0;

    public function __construct($instance, $function, $new)
    {
        if ($new === true) {
            self::$js_top = "";
            self::$js_content = "";
            self::$js_bottom = "";
            self::$js_vars = "\nvar __checksum = 0;\n";
            self::$vars = array();
            self::$_count = 0;
        }
        self::$js_top = "//<![CDATA[\n/* Javascript function for form created by class Form\n * Class Form is part of VFramework and VFramework is part of VisionCMS\n */\n\nfunction " .
            $function . $instance . "(){\n";
    }

    public static function createIf($name, $operator, $value, $message)
    {
        self::createVarByName($name);
        $temp = (string )"";
        $temp .= "\nif(" . $name . "[0].value " . $operator . " '" . $value . "'){\n   alert('" .
            $message . "');\n   return false;\n}\nelse{\n__checksum++;\n}\n";
        self::$js_content .= $temp;
        self::$_count++;
    }

    public static function createIfForCheckbox($name, $message, $neg = true)
    {
        self::createVarByName($name);

        $neg = ($neg) ? '!' : '';

        $temp .= "\nif(" . $neg . $name . "[0].checked){\n   alert('" . $message . "');\n   return false;\n}\nelse{\n__checksum++;\n}\n";
        self::$js_content .= $temp;
        self::$_count++;
    }

    public static function createIfForTextarea($name, $message, $operator, $to = "",
        $neg = true)
    {
        self::createVarByName($name);

        $neg = ($neg) ? '!' : '';

        $temp .= "\nif(" . $neg . $name . "[0].value " . $operator . " '" . $to . "'){\n   alert('" .
            $message . "');\n   return false;\n}\nelse{\n__checksum++;\n}\n";
        self::$js_content .= $temp;
        self::$_count++;
    }

    public static function createIfForIsNumeric($name, $message)
    {
        self::createVarByName($name);
        $temp .= "\nif(" . $name . "[0].value != parseInt(" . $name . "[0].value)){\n   alert('" .
            $message . "');\n   return false;\n}\nelse{\n__checksum++;\n}\n";
        self::$js_content .= $temp;
        self::$_count++;
    }

    public static function createIfForRegex($name, $regex, $message)
    {
        self::createVarByName($name);
        $temp .= "\nif(!" . $name . "[0].value.match(" . $regex . ")){\n   alert('" . $message .
            "');\n   return false;\n}\nelse{\n__checksum++;\n}\n";
        self::$js_content .= $temp;
        self::$_count++;
    }

    public static function createMinLength($name, $min, $message)
    {
        self::createVarByName($name);
        $temp .= "\nif(" . $name . "[0].value.length < " . $min . "){\n   alert('" . $message .
            "');\n   return false;\n}\nelse{\n__checksum++;\n}\n";
        self::$js_content .= $temp;
        self::$_count++;
    }

    public static function createVarByName($name)
    {
        if (array_key_exists($name, self::$vars)) {
            return true;
        } else {
            self::$vars[$name] = '';
            $temp = "\nvar " . $name . " = document.getElementsByName('" . $name . "');\n";
            self::$js_vars .= $temp;
        }
    }

    public static function createAjaxFunction()
    {
        //TODO:Create Ajax function handler
    }

    public static function getJs()
    {
        self::$js_bottom = "\n if(__checksum == " . self::$_count . "){\nreturn true;\n}\n\n }\n//]]>";
        return "<script type=\"text/javascript\">\n" . self::$js_top . self::$js_vars .
            self::$js_content . self::$js_bottom . "\n</script>\n";
    }

}


class Form_vars
{
    const Email = '/^[^@]*@[^@]*\.[^@]*$/i';
    const Url = '/^[a-zA-Z]+[:\/\/]+[A-Za-z0-9\-_]+\\.+[A-Za-z0-9\.\/%&=\?\-_]+$/i';
    const DateFormat = '/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/';
    const IPv4 = "/^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}$/";
    const Telephone = "/^[+]?[()/0-9. -]{9,}$/";
}
?>